<!DOCTYPE html>




<html lang="en" dir="ltr" itemscope itemtype="http://schema.org/Article">

<head>
    <meta charset="utf-8">
    <meta property="twitter:account_id" content="1593210261" />
    <!-- Copyright (c) 2012 Google Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * Author: Paul Lewis - website@aerotwist.com
   *
   * 
   *
  -->
    <title>High DPI Canvas - HTML5 Rocks</title>
    <meta name="description" content="High density displays are here on many devices. The canvas element deals with that in different ways in different browsers - find out how.">
    <meta name="keywords" content="html5,html 5,html5 demos,html5 examples,javascript,css3,notifications,geolocation,web workers,apppcache,file api,filereader,indexeddb,offline,audio,video,drag and drop,chrome,sse,mobile">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
    <meta name="theme-color" content="#f04530">
    <link rel="shortcut icon" href="/favicon.ico">
    <link rel="alternate" type="application/rss+xml" title="HTML5 Rocks RSS" href="http://feeds.feedburner.com/html5rocks">
    <meta name="google-site-verification" content="E1HOIDkksrWY5npenL8FeQhKn4Ujctd75iO2lfufSyA" />
    <meta itemprop="name" content="High DPI Canvas - HTML5 Rocks">
    <meta itemprop="description" content="High density displays are here on many devices. The canvas element deals with that in different ways in different browsers - find out how.">

    <meta itemprop="image" content="https://www.html5rocks.com/static/images/html5rocks-logo-wings.png">




    <link rel="canonical" href="https://www.html5rocks.com/tutorials/canvas/hidpi/">




    <meta name="twitter:card" content="summary">
    <meta name="twitter:site" content="@ChromiumDev">
    <meta name="twitter:creator" content="@aerotwist">

    <meta property="og:type" content="article">
    <meta property="og:title" content="High DPI Canvas - HTML5 Rocks">
    <meta property="og:url" content="https://www.html5rocks.com/en/tutorials/canvas/hidpi/">
    <meta property="og:description" content="High density displays are here on many devices. The canvas element deals with that in different ways in different browsers - find out how.">
    <meta property="og:image" content="https://www.html5rocks.com/static/images/profiles/paullewis.png">
    <meta property="og:site_name" content="HTML5 Rocks - A resource for open web HTML5 developers">



    <link rel="author" href="https://www.google.com/profiles/105201233571140699617">


    <link rel="publisher" href="https://plus.google.com/+GoogleChromeDevelopers">





    <link rel="stylesheet" media="all" href="/static/css/v2-combined.min.css?20131111">





    <link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,400italic,600,800|Source+Code+Pro" rel="stylesheet">

    <link rel="apple-touch-icon" href="/static/images/identity/HTML5_Badge_64.png">
    <link rel="apple-touch-icon-precomposed" href="/static/images/identity/HTML5_Badge_64.png">

    <script src="/static/js/modernizr.custom.82437.js"></script>

    <!--[if lt IE 9]>
  <script src="http://html5shim.googlecode.com/svn/trunk/html5-els.js"></script>
  <![endif]-->


</head>

<body data-href="tutorials-canvas-hidpi" onload="" class="article tutorial">

    <header class="main" id="siteheader">
        <h1 id="title">
            <a href="/en/" title="HTML5 Rocks">HTML5 Rocks</a>
        </h1>
        <a href="#sitenav" id="navtoggle">Show navigation</a>


        <a id="toctoggle" href="#toc">Table of Contents</a>


        <nav id="sitenav">
            <ul>

                <li id="home_menu">
                    <a href="/en/" class="home">Home</a>
                </li>

                <li id="tutorials_menu">
                    <a href="/en/tutorials/?page=1" class="tutorials">Tutorials</a>
                </li>
                <li id="updates_menu">
                    <a href="https://developers.google.com/web/updates/" class="updates">Updates</a>
                </li>
            </ul>
        </nav>


        <nav class="toc" id="toc">
            <h1>Table of Contents</h1>

            <ul>
                <li>
                    <a href='#toc-intro'>Introduction</a>
                </li>
                <li>
                    <a href='#toc-1'>The devicePixelRatio property</a>
                </li>
                <li>
                    <a href='#toc-2'>Introducing the backing store</a>
                </li>
                <li>
                    <a href='#toc-3'>Implementation Differences</a>
                </li>
            </ul>

            <h1 class="visible-title">Localizations:</h1>
            <ul>

                <li>
                    <a href="https://github.com/html5rocks/www.html5rocks.com/blob/master/CONTRIBUTING.md">Contribute one</a>
                </li>

            </ul>
        </nav>

    </header>

    <div class="body-content">


        <section class="title">



            <section class="title-text container">

                <h1>High DPI Canvas</h1>



                <a href="/en/" class="watermark">HTML5 Rocks</a>
            </section>
        </section>

        <article class="content-wrapper">

            <section class="container">



                <div class="article-meta" id="article-meta">
                    <nav class="toc">
                        <h1>Table of Contents</h1>

                        <ul>
                            <li>
                                <a href='#toc-intro'>Introduction</a>
                            </li>
                            <li>
                                <a href='#toc-1'>The devicePixelRatio property</a>
                            </li>
                            <li>
                                <a href='#toc-2'>Introducing the backing store</a>
                            </li>
                            <li>
                                <a href='#toc-3'>Implementation Differences</a>
                            </li>
                        </ul>
                    </nav>

                    <aside class="localizations">
                        <h1>Localizations</h1>
                        <ul>

                            <li>
                                <a href="https://github.com/html5rocks/www.html5rocks.com/blob/master/CONTRIBUTING.md">Contribute one</a>
                            </li>

                        </ul>
                    </aside>
                </div>


                <div class="content" id="article-content">

                    <section class="byline">

                        <div class="byline-content">

                            <section class="author-images">
                                <a href="/profiles/#paullewis">
                                    <img src="/static/images/profiles/paullewis.png" itemprop="photo" alt="Paul Lewis" title="Paul Lewis">
                                </a>


                            </section>

                            <section class="meta">
                                <div class="authors">
                                    <strong>By</strong>
                                    <a href="/profiles/#paullewis">Paul Lewis</a>

                                </div>



                                <div class="date">
                                    <time pubdate>
                                        <strong>Published:</strong> August 25th, 2012</time>

                                    <span>
                                        <strong>Comments:</strong>
                                        <a href="#disqus_thread" class="load-comments" data-disqus-identifier="https://www.html5rocks.com/tutorials/canvas/hidpi/">0</a>
                                    </span>
                                </div>

                                <div id="notcompatible" class="hidden">
                                    Your browser may not support the functionality in this article.
                                </div>
                            </section>
                            <div class="clear"></div>


                        </div>
                    </section>



                    <h2 id="toc-intro">Introduction</h2>

                    <p>HiDPI screens are lovely, they make everything look smoother and cleaner. But they also present a new
                        set of challenges to developers. In this article we are going to take a look into the unique challenges
                        of drawing images in the canvas in the context of HiDPI screens.</p>

                    <h2 id="toc-1">The devicePixelRatio property</h2>

                    <p>Let's start at the beginning. Back before we had HiDPI screens a pixel was a pixel (if we ignore zooming
                        and scaling for a bit) and that was it, you didn't really need to change anything around. If you
                        set something to be 100px wide that was all there was to it. Then the first few HiDPI mobile handsets
                        started popping up with the slightly enigmatic devicePixelRatio property on the window object and
                        available for use in media queries. What this property allowed us to do was understand the ratio
                        of how pixel values (which we call the logical pixel value) in - say - CSS would translate to the
                        <em>actual</em> number of pixels the device would use when it came to rendering. In the case of an iPhone
                        4S, which has a devicePixelRatio of 2, you will see that a 100px logical value equates to a 200px
                        device value.</p>

                    <p>That's interesting, but what does that mean for us developers? Well in the early days we all started
                        to notice that our images were being upscaled by these devices. We were creating images at the logical
                        pixel width of our elements and, when they were drawn out, they would be upscaled by the devicePixelRatio
                        and they'd be blurry.</p>

                    <figure>
                        <img src="html5rocks-blurry.png" />
                        <figcaption>Figure 1 - An image being upscaled and blurred due to the devicePixelRatio</figcaption>
                    </figure>

                    <p>The de facto solution to this has been to create images scaled up by the devicePixelRatio and then use
                        CSS to scale it down by the same amount. So when the device scales it back up using the devicePixelRatio
                        it won't have been scaled beyond its original size and it will no longer be blurry. Problem solved.
                        (Of course that in general terms probably means you're sending images that are overly large for normal
                        DPI devices, but that's a different issue!)</p>

                    <h2 id="toc-2">Introducing the backing store</h2>

                    <p>What about the canvas? Well that's what we will cover in this article, and to warn you upfront we will
                        be primarily marking out an implementation difference between Chrome and Safari 6 on desktop. There
                        is a newly-exposed, prefixed (and verbosely named) property on each canvas context: webkitBackingStorePixelRatio.
                        Right now Mozilla, Opera and Microsoft do not have an analogous property available in their canvas
                        contexts, but it may well appear in the future.</p>

                    <p>With that said, the webkitBackingStorePixelRatio property tells us in the given browser what the backing
                        store size is in relation to the canvas element itself. In case you're wondering what the backing
                        store is, whenever you draw anything into the canvas's context, you're really having the browser
                        write it to the underlying storage for the canvas (called its backing store). When the browser comes
                        to draw the canvas to the screen it uses the backing store's data. That means that webkitBackingStorePixelRatio
                        tells us the dimensions of the backing store in relation to the canvas's dimensions. In case you're
                        wondering why you can't use the devicePixelRatio to determine the backing store size, the answer
                        is that they aren't guaranteed to match. Despite presenting the same devicePixelRatio value, Chrome
                        and Safari 6 can and do have entirely different approaches for the backing store size (and therefore
                        the webkitBackingStorePixelRatio) on HiDPI devices. The net result is that we can't rely on devicePixelRatio
                        to know how the browser is going to scale images that are written into the canvas.</p>

                    <p>OK, so we know what the webkitBackingStorePixelRatio is, but we need to know what its implications are.
                        For the sake of simplicity let's say we have a canvas that is
                        <strong>200px wide</strong> and a webkitBackingStorePixelRatio value of
                        <strong>2</strong>. Our underlying backing store, therefore, will be
                        <strong>400px wide</strong>. It's worth noting that there's nothing to say for sure that the ratio will be
                        2, it could just as easily be some other value, depending on the browser and device.</p>

                    <figure>
                        <img src="canvas-backingstore.png" />
                        <figcaption>Figure 2 - The canvas element and how it relates to its backing store</figcaption>
                    </figure>

                    <p>When the browser gets to drawing out the canvas it is scaled down according to its logical pixel value
                        of
                        <strong>200px</strong>, the value that we chose earlier. But then the devicePixelRatio takes over and it
                        will be scaled back up again. If we assume that our devicePixelRatio is also 2, the same as our webkitBackingStoreRatio,
                        it will be
                        <strong>400px</strong> wide. Again, its might not be 2; the Nexus 7, for instance, has a devicePixelRatio
                        of around 1.325. You'd be forgiven for being very confused at this point, but you can think of it
                        like this:</p>

                    <figure>
                        <img src="canvas-backingstore-device.png" />
                        <figcaption>Figure 3 - The canvas element being scaled and rescaled</figcaption>
                    </figure>

                    <p>Now we're getting closer. We know what the webkitBackingStorePixelRatio and devicePixelRatio values are
                        used for, but we now need to talk about the implementation differences.</p>

                    <h2 id="toc-3">Implementation Differences</h2>

                    <p>On a HiDPI device such as a Macbook Pro with Retina display, Safari 6 carries a webkitBackingStorePixelRatio
                        value of
                        <strong>2</strong> and a devicePixelRatio of 2 whereas Chrome uses a webkitBackingStorePixelRatio value of
                        <strong>1</strong> and a devicePixelRatio of 2. This means that if you draw an image into a canvas in Safari
                        it will
                        <em>automatically</em> double the dimensions of the image when writing it to the canvas's backing store,
                        so after scaling down to the logical pixel size and back up again through the devicePixelRatio you
                        will arrive back at the size you specified. In Chrome, however, the image will be written to the
                        backing store at the exact size you specify which means that after the devicePixelRatio is applied
                        it will be upscaled and blurry. A picture speaks a thousand words, so below is an HTML5 Rocks image
                        that has been drawn into a canvas:</p>

                    <figure>
                        <img src="comparison.png" />
                        <figcaption>Figure 4 - Side by side comparison of HiDPI rendering in Chrome and Safari 6</figcaption>
                    </figure>

                    <p>You'll notice that by default Chrome's rendering is blurry compared to Safari 6's because our image is
                        being written into the canvas backing store at the width we specify then upscaled by the devicePixelRatio.</p>

                    <p>That leaves two questions:
                        <em>why does Chrome
                            <strong>not</strong> automatically upscale backing stores in the same way as Safari 6</em> and
                        <em>how can you as a developer ensure that you can draw images at the scale you choose?</em>
                    </p>

                    <p>Let's deal with the automatic upscaling question first. As I mentioned earlier there is no guarantee
                        that the backing store ratio will be 2, it could be completely different, possibly more than 2. But
                        if you consider a value of 2 then your backing store will be double the canvas size in both width
                        and height, and that means four times the amount of memory is required to service your canvas element.
                        If you have multiple canvas elements or your code is running in a memory-constrained environment
                        such as on mobile then you're more likely to exhaust your resources. By not doing the upscaling automatically
                        you have the option to do it or not, it's totally your call.</p>

                    <p>That leaves us with what you can do to manually upscale your canvas. The answer is pretty simple: upsize
                        your canvas width and height by devicePixelRatio / webkitBackingStorePixelRatio and then use CSS
                        to scale it back down to the logical pixel size you want. Taking our above case where Chrome reports
                        a webkitBackingStorePixelRatio of 1 and a devicePixelRatio of 2 we would scale the dimensions of
                        the canvas by 2 / 1, i.e. multiply them by 2, then we would use CSS to scale it back down.</p>

                    <p>Finally the last thing we need to account for is that since we have scaled up our canvas manually (and
                        reduced it back down using CSS) we now have to make sure we scale up the width, height and positions
                        of our images proportionally.</p>

                    <p>The code for that looks like this:</p>

                    <pre class="prettyprint">
/**
 * Writes an image into a canvas taking into
 * account the backing store pixel ratio and
 * the device pixel ratio.
 *
 * @author Paul Lewis
 * @param {Object} opts The params for drawing an image to the canvas
 */
function drawImage(opts) {

    if(!opts.canvas) {
        throw("A canvas is required");
    }
    if(!opts.image) {
        throw("Image is required");
    }

    // get the canvas and context
    var canvas = opts.canvas,
        context = canvas.getContext('2d'),
        image = opts.image,

    // now default all the dimension info
        srcx = opts.srcx || 0,
        srcy = opts.srcy || 0,
        srcw = opts.srcw || image.naturalWidth,
        srch = opts.srch || image.naturalHeight,
        desx = opts.desx || srcx,
        desy = opts.desy || srcy,
        desw = opts.desw || srcw,
        desh = opts.desh || srch,
        auto = opts.auto,

    // finally query the various pixel ratios
        devicePixelRatio = window.devicePixelRatio || 1,
        backingStoreRatio = context.webkitBackingStorePixelRatio ||
                            context.mozBackingStorePixelRatio ||
                            context.msBackingStorePixelRatio ||
                            context.oBackingStorePixelRatio ||
                            context.backingStorePixelRatio || 1,

        ratio = devicePixelRatio / backingStoreRatio;

    // ensure we have a value set for auto.
    // If auto is set to false then we
    // will simply not upscale the canvas
    // and the default behaviour will be maintained
    if (typeof auto === 'undefined') {
        auto = true;
    }

    // upscale the canvas if the two ratios don't match
    if (auto && devicePixelRatio !== backingStoreRatio) {

        var oldWidth = canvas.width;
        var oldHeight = canvas.height;

        canvas.width = oldWidth * ratio;
        canvas.height = oldHeight * ratio;

        canvas.style.width = oldWidth + 'px';
        canvas.style.height = oldHeight + 'px';

        // now scale the context to counter
        // the fact that we've manually scaled
        // our canvas element
        context.scale(ratio, ratio);

    }

    context.drawImage(pic, srcx, srcy, srcw, srch, desx, desy, desw, desh);
}
</pre>

                    <p>If you want to see what that looks like (and you have access to a HiDPI screen) take a look at
                        <a href="demo/demo.html">the demo page</a> which shows the code automatically scaling the canvas in Chrome (as well as ensuring
                        the image is drawn at the correct size and position) while leaving Safari 6 unaffected. It's important
                        that we only apply this in the situation where the backing store is not automatically scaling up
                        content because if we manually upscale it and then the browser does the same we would find ourselves
                        in the position where the content is upscaled twice which could push memory consumption through the
                        roof.</p>

                    <p>Going forward we are going to see an increasing number of devices and screen ratios. Understanding how
                        the browser manipulates the images and canvases in your applications through the backing store's
                        pixel ratio and the device pixel ratio is key to ensuring the best possible performance and quality.</p>


                </div>
            </section>
        </article>


        <section class="disqus pattern-bg-lighter">

            <div id="disqus" class="container">

                <h2>Comments</h2>

                <div id="disqus_thread">

                    <a href="#disqus_thread" class="load-comments" data-disqus-identifier="https://www.html5rocks.com/tutorials/canvas/hidpi/">0</a>

                </div>
            </div>

            <noscript>
                <p class="center">
                    <strong>
                        <a href="https://disqus.com/?ref_noscript">Please enable JavaScript to view the comments powered by Disqus.</a>
                    </strong>
                </p>
            </noscript>

            <script>
                var disqus_shortname = 'html5rocks';
                var disqus_identifier = 'https://www.html5rocks.com/tutorials/canvas/hidpi/';
                var disqus_url = 'https://www.html5rocks.com/tutorials/canvas/hidpi/';
                var disqus_developer = 0;

                var disqus_config = function () {
                    var funky_language_code_mapping = {
                        'de': 'de_inf',
                        'es': 'es_ES',
                        'pt': 'pt_EU',
                        'sr': 'sr_CYRL',
                        'sv': 'sv_SE',
                        'zh': 'zh_HANT'
                    };
                    this.language = funky_language_code_mapping['en'] ||
                        'en';

                    this.callbacks.onReady = [function () {
                        try {
                            ga('send', 'event', 'View comments');
                        } catch (err) {}
                    }];
                    this.callbacks.onNewComment = [function (comment) {
                        try {
                            ga('send', 'event', 'Commented');
                        } catch (err) {}
                    }];
                };

                window.addEventListener('load', function (e) {

                    var c = document.createElement('script');
                    c.type = 'text/javascript';
                    c.src = 'https://' + disqus_shortname + '.disqus.com/count.js';
                    c.async = true;

                    var s = document.getElementsByTagName('script')[0],
                        sp = s.parentNode;
                    sp.insertBefore(c, s);

                    if (window.location.hash === '#disqus_thread')
                        loadComments();

                }, false);

                var disqus_loaded = false;

                function loadComments() {

                    if (disqus_loaded)
                        return;

                    disqus_loaded = true;

                    ga('send', 'event', 'Interactions', 'Comments', 'Comments Loaded');

                    var s = document.getElementsByTagName('script')[0],
                        sp = s.parentNode;
                    var dsq = document.createElement('script');
                    dsq.type = 'text/javascript';
                    dsq.async = true;

                    var disqusContainer = document.getElementById('disqus');
                    disqusContainer.classList.add('active');

                    dsq.src = 'https://' + disqus_shortname + '.disqus.com/embed.js';
                    sp.insertBefore(dsq, s);
                }

                function outgoing(url) {
                    try {
                        ga('send', 'event', 'Outbound Links', url);
                    } catch (err) {}
                }
                // Open external links (also that don't have a target defined) in a new tab.
                var externLinks = document.querySelectorAll('article.tutorial a[href^="http"]:not([target])');
                for (var i = 0, a; a = externLinks[i]; ++i) {
                    a.target = '_blank';
                    a.addEventListener('click', new Function('outgoing(' + '"' + a.href.replace(/.*?:\/\//g, "") + '"' +
                        ');'));
                }

                var loadCommentsButtons = document.querySelectorAll('.load-comments');
                for (var l = 0; l < loadCommentsButtons.length; l++)
                    loadCommentsButtons[l].addEventListener('click', loadComments);
            </script>
        </section>


        <footer>
            <div class="container">


                <h1>Next steps</h2>



                    <aside class="panel share">
                        <h2>Share</h2>
                        <style>
                            aside.share.native .android {
                                display: inline-block !important;
                                background-image: url(https://storage.googleapis.com/material-icons/external-assets/v4/icons/svg/ic_share_white_36px.svg) !important;
                                background-size: 48px 48px !important;
                                background-position: 0, 0 !important;
                            }
                        </style>
                        <a href="https://twitter.com/share?url=https://www.html5rocks.com/tutorials/canvas/hidpi/&text=High DPI Canvas&lang=en&via=ChromiumDev&related=ChromiumDev"
                            class="android" style="display: none;">Share</a>
                        <a href="https://twitter.com/share?url=https://www.html5rocks.com/tutorials/canvas/hidpi/&text=High DPI Canvas&lang=en&via=ChromiumDev&related=ChromiumDev"
                            class="twitter" target="_blank">Twitter</a>
                        <a href="https://www.facebook.com/sharer/sharer.php?u=https://www.html5rocks.com/tutorials/canvas/hidpi/" class="facebook"
                            target="_blank">Facebook</a>
                        <a href="https://plus.google.com/share?url=https://www.html5rocks.com/tutorials/canvas/hidpi/" class="gplus" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;">Google+</a>
                    </aside>

                    <aside class="panel rss">
                        <h2>Subscribe</h2>
                        <p>Enjoyed this article? Grab the
                            <a href="http://feeds.feedburner.com/html5rocks">RSS feed</a> and stay up-to-date.</p>
                    </aside>



                    <p class="licensing">

                        Except as otherwise
                        <a href="http://code.google.com/policies.html#restrictions">noted</a>, the content of this page is licensed under the
                        <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 License</a>, and code samples are licensed under the
                        <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>.

                    </p>

            </div>
        </footer>

        <script>
            window.isCompatible = function () {

                return !!Modernizr.canvas

            };

            if (navigator.share !== undefined) {
                document.addEventListener('DOMContentLoaded', e => {
                    var shareBox = document.querySelector('aside.share');
                    if (shareBox !== undefined) {
                        shareBox.classList.toggle('native');
                    }

                    var shareBtn = document.querySelector('aside.share a.android');
                    shareBtn.addEventListener('click', clickEvent => {
                        clickEvent.preventDefault();
                        const title = "High DPI Canvas";
                        const url = "https://www.html5rocks.com/tutorials/canvas/hidpi/";
                        navigator.share({
                                title: title,
                                text: url,
                                url: url
                            })
                            .then(() => console.log('Successful share'),
                                error => console.log('Error sharing:', error));
                    });
                });
            }

            if (isCompatible() === false) {
                document.getElementById('notcompatible').className = '';
            }

            function _prettyPrint() {
                if (typeof customPrettyPrintLanguage != 'undefined') {
                    customPrettyPrintLanguage();
                }
                prettyPrint();
            }
        </script>
        <script async src="/static/js/prettify.min.js" onload="_prettyPrint()"></script>
        <!-- Google Tag Manager -->
        <noscript>
            <iframe src="//www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe>
        </noscript>
        <script>
            (function (w, d, s, l, i) {
                w[l] = w[l] || [];
                w[l].push({
                    'gtm.start': new Date().getTime(),
                    event: 'gtm.js'
                });
                var f = d.getElementsByTagName(s)[0],
                    j = d.createElement(s),
                    dl = l != 'dataLayer' ? '&l=' + l : '';
                j.async = true;
                j.src =
                    '//www.googletagmanager.com/gtm.js?id=' + i + dl;
                f.parentNode.insertBefore(j, f);
            })(window, document, 'script', 'dataLayer', 'GTM-MB3LRF');
        </script>
        <!-- End Google Tag Manager -->


    </div>

    <script>
        (function () {

            // Kill feedburner and marketing tracking arguments, but let them register
            // before we do it.
            setTimeout(function () {
                if (/^\?utm_/.test(document.location.search) &&
                    window.history.replaceState) {
                    window.history.replaceState({}, '', document.location.href.replace(/\?utm_.*/, ''));
                }
            }, 2000);

            var siteHeader = document.getElementById('siteheader');
            var navToggle = document.getElementById('navtoggle');
            var siteNav = document.getElementById('sitenav');

            function toggle(target, forceActive) {

                if (typeof toc !== 'undefined') {
                    // Switch off whichever one is not the
                    // current target
                    if (target === toc)
                        siteNav.classList.remove('active');
                    else
                        toc.classList.remove('active');
                }

                // Toggle if no force parameter is set
                if (typeof forceActive === 'undefined') {
                    target.classList.toggle('active');
                } else {
                    if (forceActive)
                        target.classList.add('active');
                    else
                        target.classList.remove('active');
                }

                // now find out what the set state ended up being
                var isActive = target.classList.contains('active');

                if (isActive)
                    siteHeader.classList.add('expanded');
                else
                    siteHeader.classList.remove('expanded');

            }

            navToggle.addEventListener('click', function (e) {
                toggle(siteNav);
                e.preventDefault();
            });



            var tocToggle = document.getElementById('toctoggle');
            var toc = document.getElementById('toc');
            var articleMeta = document.getElementById('article-meta');
            var articleContent = document.getElementById('article-content');
            var articleMetaHeight = 0;
            var articleMetaMaxY = 0;
            var articleMetaMinY = 0;
            var articleContentPadding = 200;

            var tocLinks = document.querySelectorAll('.toc a');
            for (var t = 0; t < tocLinks.length; t++)
                tocLinks[t].addEventListener('click', onTocLinkClick);

            tocToggle.addEventListener('click', function (e) {
                toggle(toc);
                e.preventDefault();
            });

            toc.addEventListener('click', function (e) {
                if (e.target !== siteNav)
                    toggle(toc, false);
            });

            function onTocLinkClick() {
                ga('send', 'event', 'Interactions', 'TOC', 'TOC Clicked');
            }

            function setMinScrollYFromMetaY() {
                var scrollPosition = window.scrollY;

                var articleMetaBounds = articleMeta.getBoundingClientRect();
                var articleMetaTop = Math.max(352,
                    articleMetaBounds.top - 20 + scrollPosition);

                articleMetaHeight = articleMetaBounds.bottom - articleMetaBounds.top;
                articleMetaMinY = articleMetaTop;
            }

            function setMaxScrollYFromContentHeight() {

                var scrollPosition = window.scrollY;

                var articleContentBounds = articleContent.getBoundingClientRect();
                var articleContentTop = articleContentBounds.top + scrollPosition;
                var articleContentHeight = articleContentBounds.bottom - articleContentBounds.top;

                articleMetaMaxY = articleContentTop +
                    articleContentHeight -
                    articleMetaHeight -
                    articleContentPadding;

            }

            function onScroll(e) {

                if (window.scrollY >= articleMetaMinY) {

                    articleMeta.classList.add('sticky');

                    var articleMetaTop = 22 - Math.max(0, window.scrollY - articleMetaMaxY);
                    articleMeta.style.top = articleMetaTop + 'px';

                } else {
                    articleMeta.classList.remove('sticky');
                    articleMeta.style.top = 'auto';
                }
            }

            if (articleMeta.getBoundingClientRect) {
                setMinScrollYFromMetaY();
                setMaxScrollYFromContentHeight();
                document.addEventListener('scroll', onScroll);
                window.addEventListener('load', setMaxScrollYFromContentHeight, false);
            }


        })();
    </script>
    <script>
        (function (i, s, o, g, r, a, m) {
            i['GoogleAnalyticsObject'] = r;
            i[r] = i[r] || function () {
                (i[r].q = i[r].q || []).push(arguments)
            }, i[r].l = 1 * new Date();
            a = s.createElement(o),
                m = s.getElementsByTagName(o)[0];
            a.async = 1;
            a.src = g;
            m.parentNode.insertBefore(a, m)
        })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

        ga('create', 'UA-15028909-1', 'auto');
        ga('create', 'UA-49880327-4', 'auto', {
            'name': 'html5rocks'
        });

        ga('send', 'pageview');
        ga('html5rocks.send', 'pageview');
    </script>
    <!-- Google Tag Manager -->
    <noscript>
        <iframe src="//www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe>
    </noscript>
    <script>
        (function (w, d, s, l, i) {
            w[l] = w[l] || [];
            w[l].push({
                'gtm.start': new Date().getTime(),
                event: 'gtm.js'
            });
            var f = d.getElementsByTagName(s)[0],
                j = d.createElement(s),
                dl = l != 'dataLayer' ? '&l=' + l : '';
            j.async = true;
            j.src =
                '//www.googletagmanager.com/gtm.js?id=' + i + dl;
            f.parentNode.insertBefore(j, f);
        })(window, document, 'script', 'dataLayer', 'GTM-MB3LRF');
    </script>
    <!-- End Google Tag Manager -->
</body>

</html>